package com.madding.shared.encrypt.cert.gen;

import java.math.BigInteger;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.bouncycastle.asn1.DERBMPString;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.jce.PrincipalUtil;
import org.bouncycastle.jce.X509Principal;
import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.x509.X509V1CertificateGenerator;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;

import com.madding.shared.encrypt.cert.bc.util.X509PrincipalUtil;

/**
 * 证书创建工厂,用于处理证书的创建工作
 * 
 * @author madding.lip
 */
public class BCCertGenerator {

    public static final String                  BC_JCE             = "BC";
    public static final String                  CA_ALIAS           = "alilang";

    public static final String                  ALILANG_CA         = "Alilang CA.";
    public static final String                  ALILANG_CLIENT_CA  = "Alilang Client CA";

    public static final String                  SHA1_RSA           = "SHA1WithRSAEncryption";

    public static final long                    ROOT_CERT_INDATE   = 20 * 365 * 24 * 60L * 60 * 1000L;
    public static final long                    CLIENT_CERT_INDATE = 5 * 365 * 24 * 60L * 60 * 1000L;

    private static ThreadLocal<BCCertGenerator> threadlocal        = new ThreadLocal<BCCertGenerator>();

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    public static BCCertGenerator getIns() {
        if (threadlocal.get() == null) {
            threadlocal.set(new BCCertGenerator());
        }
        return threadlocal.get();
    }

    public Certificate createCaCert(KeyPair keyPair) throws Exception {

        PublicKey pubKey = keyPair.getPublic();
        PrivateKey privKey = keyPair.getPrivate();

        X509V1CertificateGenerator v1CertGen = new X509V1CertificateGenerator();

        v1CertGen.setSerialNumber(BigInteger.valueOf(1));
        v1CertGen.setIssuerDN(X509PrincipalUtil.createMasterPrincipal());
        Date firstDate = new Date();
        Date lastDate = new Date(firstDate.getTime() + ROOT_CERT_INDATE);
        v1CertGen.setNotBefore(firstDate);
        v1CertGen.setNotAfter(lastDate);
        v1CertGen.setSubjectDN(X509PrincipalUtil.createMasterPrincipal());
        v1CertGen.setPublicKey(pubKey);
        v1CertGen.setSignatureAlgorithm(SHA1_RSA);

        X509Certificate cert = v1CertGen.generate(privKey, BC_JCE);

        cert.checkValidity(new Date());

        cert.verify(pubKey);

        PKCS12BagAttributeCarrier bagAttr = (PKCS12BagAttributeCarrier) cert;

        bagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString(ALILANG_CA));

        return cert;
    }

    /**
     * we generate an intermediate certificate signed by our CA
     */
    public Certificate createClientCaCert(KeyPair keyPair, PrivateKey caPrivKey, X509Certificate caCert)
                                                                                                        throws Exception {

        PublicKey pubKey = keyPair.getPublic();

        X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator();
        v3CertGen.reset();

        // create the certificate - version 3
        v3CertGen.setSerialNumber(BigInteger.valueOf(2));
        v3CertGen.setIssuerDN(PrincipalUtil.getSubjectX509Principal(caCert));
        Date firstDate = new Date();
        Date lastDate = new Date(firstDate.getTime() + ROOT_CERT_INDATE);
        v3CertGen.setNotBefore(firstDate);
        v3CertGen.setNotAfter(lastDate);
        v3CertGen.setSubjectDN(X509PrincipalUtil.createClientIntermediatePrincipal());
        v3CertGen.setPublicKey(pubKey);
        v3CertGen.setSignatureAlgorithm(SHA1_RSA);

        // extensions
        v3CertGen.addExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(pubKey));
        v3CertGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
                               new AuthorityKeyIdentifierStructure(caCert));
        v3CertGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(0));

        X509Certificate cert = v3CertGen.generate(caPrivKey, BC_JCE);
        cert.checkValidity(new Date());
        cert.verify(caCert.getPublicKey());

        PKCS12BagAttributeCarrier bagAttr = (PKCS12BagAttributeCarrier) cert;
        bagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString(ALILANG_CLIENT_CA));

        return cert;
    }

    public Certificate createClientUserCert(String subjectAltName, X509Principal subject, Map<String, String> exts,
                                            PublicKey pubKey, KeyPair caKeyPair) throws Exception {

        PublicKey caPubKey = caKeyPair.getPublic();
        PrivateKey caPrivKey = caKeyPair.getPrivate();

        X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator();
        v3CertGen.reset();

        // create the certificate - version 3
        v3CertGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
        v3CertGen.setIssuerDN(X509PrincipalUtil.createClientIntermediatePrincipal());
        Date firstDate = new Date();
        Date lastDate = new Date(firstDate.getTime() + CLIENT_CERT_INDATE);
        v3CertGen.setNotBefore(firstDate);
        v3CertGen.setNotAfter(lastDate);
        v3CertGen.setSubjectDN(subject);
        v3CertGen.setPublicKey(pubKey);
        v3CertGen.setSignatureAlgorithm(SHA1_RSA);

        // add the extensions
        Set<String> key = exts.keySet();
        for (Iterator<String> it = key.iterator(); it.hasNext();) {
            String oid = it.next();
            String value = exts.get(oid);
            v3CertGen.addExtension(oid, false, new DEROctetString(value.getBytes()));
        }

        v3CertGen.addExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(pubKey));
        v3CertGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(caPubKey));

        X509Certificate cert = v3CertGen.generate(caPrivKey, BC_JCE);
        cert.checkValidity(new Date());
        cert.verify(caPubKey);

        PKCS12BagAttributeCarrier bagAttr = (PKCS12BagAttributeCarrier) cert;
        bagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString(subjectAltName));
        bagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, new SubjectKeyIdentifierStructure(pubKey));

        return cert;
    }

}
